﻿//+-------------------------------------------------------------------------------------------------------------------------------------------------------
//
//Description: AzureHelper
//Copyright © Microsoft Corporation
//Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
//You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 
//
//THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, 
//INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. 
//See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
// 
//--------------------------------------------------------------------------------------------------------------------------------------------------------
using System;
using System.Diagnostics;
using System.Net;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Security.Cryptography.X509Certificates;

using MessageBox = System.Windows.MessageBox;

using Microsoft.WindowsAzure.Storage.Blob;

using VmroleAuthor.Models;

namespace VmroleAuthor.ViewModels
{
    public class AzureHelper
    {
        internal static readonly string MSEAHName = "MSEAHName";
        internal static readonly string MSEAHPublisherName = "MSEAHPublisherName";
        internal static readonly string MSEAHVersion = "MSEAHVersion";
        internal static readonly string AzureApiVersion = "AzureAPIVersion";
        internal static readonly string FrontEndpoint = "FrontEndpoint";
        internal static readonly string SubscriptionId = "SubscriptionId";
        internal static readonly string MgtCertThumbprint= "MgtCertThumbprint";
        internal static readonly string ValidateServerCertificate = "ValidateServerCertificate";

        // Azure API Version
        private static readonly string ApiVersion;

        // Azure Storage delimiter
        internal static readonly string DeLimiter = "/";

        // MS EAH values
        internal static readonly string MSExtAppHandlerName;
        internal static readonly string MSExtAppHandlerPublisherName;
        private static string _msExtAppHandlerVersion;

        // create a new ResourceExtReferences node
        private const string ResourceExtReferences = "<ResourceExtensionReferences xmlns=\"{0}\">" + "{1}" + "</ResourceExtensionReferences>";

        // the current MS EAH ResourceExtReference node
        private const string ResourceExtReference = "<ResourceExtensionReference xmlns=\"{0}\">" + " <ReferenceName>myMSEAHandler</ReferenceName>" + " <Publisher>{1}</Publisher>" + " <Name>{2}</Name>" + " <Version>{3}</Version>" + " <ResourceExtensionParameterValues>" + "  <ResourceExtensionParameterValue>" + "     <Key>privateParameter</Key>" + "     <Value>{4}</Value>" + "     <Type>Private</Type>" + "  </ResourceExtensionParameterValue>" + " </ResourceExtensionParameterValues>" + " <State>Enable</State>" + "</ResourceExtensionReference>";

        // the ResourceExtReferenceParamValues node
        private const string ResourceExtReferenceParamValues = "<ResourceExtensionParameterValues xmlns=\"{0}\">" + " <ResourceExtensionParameterValue>" + "  <Key>privateParameter</Key>" + "  <Value>{1}</Value>" + "  <Type>Private</Type>" + " </ResourceExtensionParameterValue>" + "</ResourceExtensionParameterValues>";

        // lock for EH Version
        private static readonly ReaderWriterLock RwEhvLock = new ReaderWriterLock();
        private const int RwTimeoutinMilliSeconds = 30000;

        /// <summary>
        /// static constuctor
        /// </summary>
        static AzureHelper()
        {
            // set up the static values
            MSExtAppHandlerName = "MSEnterpriseApplication";
            MSExtAppHandlerPublisherName = "Microsoft.SystemCenter";
            ApiVersion = @"2014-04-01";

            try
            {
                NameValueCollection appSettings = ConfigurationManager.AppSettings;
                if (appSettings.Count > 0)
                {
                    // check if MS EAH values are specified
                    // name
                    var ehName = appSettings.GetValues(MSEAHName);
                    if (ehName != null && !string.IsNullOrEmpty(ehName[0]))
                    {
                        MSExtAppHandlerName = ehName[0];
                    }
                    // publisher
                    var ehPublisherName = appSettings.GetValues(MSEAHPublisherName);
                    if (ehPublisherName != null && !string.IsNullOrEmpty(ehPublisherName[0]))
                    {
                        MSExtAppHandlerPublisherName = ehPublisherName[0];
                    }

                    // MS EAH Version to use
                    _msExtAppHandlerVersion = string.Empty;
                    var ehVersion = appSettings.GetValues(MSEAHVersion);
                    if (ehVersion != null && !string.IsNullOrEmpty(ehVersion[0]))
                    {
                        _msExtAppHandlerVersion = ehVersion[0].Trim();

                        // in case no value was specified or empty string was used or "*"
                        // we need to use the highest version of the published EHs.
                        // set the value to string.Empty to indicate this.
                        // query for the list of Versions when the configuration values are queried,
                        // do not query here as it is static constructor.
                        if (string.IsNullOrEmpty(_msExtAppHandlerVersion) ||
                            StringIsEqual(_msExtAppHandlerVersion, "*"))
                        {
                            _msExtAppHandlerVersion = string.Empty;
                        }
                    }

                    // azure API version
                    var apiVersion = appSettings.GetValues(AzureApiVersion);
                    if (apiVersion != null && !string.IsNullOrEmpty(apiVersion[0]))
                    {
                        ApiVersion = apiVersion[0];
                    }
                }
            }
            catch (ConfigurationErrorsException)
            {
                // log
            }
        }

        /// <summary>
        /// MS EAH Version to use
        /// </summary>
        internal static string MSExtAppHandlerVersion
        {
            get
            {
                try
                {
                    RwEhvLock.AcquireReaderLock(RwTimeoutinMilliSeconds);
                    try
                    {
                        return _msExtAppHandlerVersion;
                    }
                    finally
                    {
                        RwEhvLock.ReleaseReaderLock();
                    }
                }
                catch(ApplicationException)
                { }
                return string.Empty;
            }
            set
            {
                try
                {
                    RwEhvLock.AcquireWriterLock(RwTimeoutinMilliSeconds);
                    try
                    {
                        _msExtAppHandlerVersion = value;
                    }
                    finally
                    {
                        RwEhvLock.ReleaseWriterLock();
                    }
                }
                catch (ApplicationException)
                { }
            }
        }

        /// <summary>
        /// get the certificate from this machine for the input thumbprint
        /// </summary>
        /// <param name="thumbprint"></param>
        /// <returns></returns>
        internal static X509Certificate2 GetCertificate(string thumbprint)
        {
            List<StoreLocation> certLocations = new List<StoreLocation>();
            certLocations.Add(StoreLocation.CurrentUser);
            certLocations.Add(StoreLocation.LocalMachine);
          

            foreach (StoreLocation storeLocation in certLocations)
            {
                X509Store certStore = new X509Store(storeLocation);

                try
                {
                    certStore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
                    X509Certificate2Collection certs = certStore.Certificates.Find(X509FindType.FindByThumbprint,
                        thumbprint, false);

                    if (certs.Count == 1)
                    {
                        return certs[0];
                    }
                }
                catch (Exception)
                {
                }
                finally
                {
                    certStore.Close();
                }
            }
            return null;
        }

        internal static bool HasAppPayload(VMRoleResourceExtensionDefinition resExt)
        {
            if (resExt.ExtensionSettings != null &&
                resExt.ExtensionSettings.ApplicationProfile != null &&
                resExt.ExtensionSettings.ApplicationProfile.ApplicationPayload != null &&
                resExt.ExtensionSettings.ApplicationProfile.ApplicationPayload.Count > 0)
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// make a rest web request
        /// </summary>
        internal static HttpWebResponse MakeRestRequest(Uri uri,
            string httpMethod, X509Certificate2 certificate, out XDocument httpResponseBody, string httpRequestBody = null)
        {
            if (certificate == null)
            {
                string msg = String.Format("VM Role Authoring Tool encountered an error using the certificate");
                MessageBox.Show(msg, DefinitionViewModel.ToolHeader, MessageBoxButton.OK, MessageBoxImage.Exclamation);
                httpResponseBody = null;
                return null;
            }

            // set up the requesturi, headers
            var restRequest = (HttpWebRequest)WebRequest.Create(uri);
            restRequest.Method = httpMethod;
            restRequest.ContentType = "application/xml";
            restRequest.Headers.Add("x-ms-version", ApiVersion);
            restRequest.ClientCertificates.Add(certificate);         

            // set up the request body
            if (httpRequestBody != null)
            {
                byte[] reqBytes = new ASCIIEncoding().GetBytes(httpRequestBody);
                restRequest.ContentLength = reqBytes.Length;
                Stream stream = restRequest.GetRequestStream();
                stream.Write(reqBytes, 0, reqBytes.Length);
                stream.Close();
            }

            // get the response
            httpResponseBody = null;
            HttpWebResponse restResponse;
            try
            {
                restResponse = (HttpWebResponse)restRequest.GetResponse();
            }
            catch (WebException ex)
            {
                restResponse = (HttpWebResponse)ex.Response;
            }

            // parse the response
            if (restResponse != null && restResponse.ContentLength > 0)
            {
                XmlReaderSettings readerSettings = new XmlReaderSettings();
                readerSettings.DtdProcessing = DtdProcessing.Ignore;
                using (XmlReader xmlReader = XmlReader.Create(input: restResponse.GetResponseStream(), settings: readerSettings))
                {
                    try
                    {
                        httpResponseBody = XDocument.Load(xmlReader);
                    }
                    catch (Exception)
                    {
                        httpResponseBody = null;
                    }
                }
            }

            if (restResponse != null)
            {
                restResponse.Close();
            }

            // return the response to the REST req
            return restResponse;
        }

        /// <summary>
        /// get the xml nodes that match an input node name
        /// </summary>
        /// <param name="xDocument"></param>
        /// <param name="nodeName"></param>
        /// <returns></returns>
        internal static List<XmlNode> GetNodes(string xDocument, string nodeName)
        {
            var sXmlDoc = new XmlDocument();

            XmlNamespaceManager nsmgr = new XmlNamespaceManager(sXmlDoc.NameTable);
            nsmgr.AddNamespace("az", "http://schemas.microsoft.com/windowsazure");

            nodeName = "//az:" + nodeName; 
            sXmlDoc.LoadXml(xDocument);
            XmlNodeList nodeList = sXmlDoc.SelectNodes(nodeName, nsmgr);
            if (nodeList != null)
            {
                return nodeList.Cast<XmlNode>().ToList();
            }
            
            return new List<XmlNode>();
        }

        /// <summary>
        /// get the xml nodes that match an input node name
        /// </summary>
        /// <param name="xNode"></param>
        /// <param name="nodeName"></param>
        /// <returns></returns>
        internal static List<XmlNode> GetNodes(XmlNode xNode, string nodeName)
        {
            return GetNodes(xNode.OuterXml, nodeName);
        }

        /// <summary>
        /// updates the status of update vm role call 
        /// </summary>
        /// <param name="deployData"></param>
        /// <param name="xDocument"></param>
        /// <param name="count"></param>
        private static void UpdateResourceExtensionStatus(ResExtAzureDeploymentData deployData, XDocument xDocument, long count)
        {
            if (xDocument.Root != null)
            {
                XNamespace xNamespace = xDocument.Root.Name.Namespace;
                var roles = from node in xDocument.Root.Element(xNamespace + "RoleInstanceList").Elements(xNamespace + "RoleInstance")
                    where StringIsEqual(node.Element(xNamespace + "RoleName").Value, deployData.SelectedVirtualMachine.Name)
                    select node;

                var enumerable = roles as XElement[] ?? roles.ToArray();
                if (enumerable.Any())
                {
                    if (enumerable.First().Element(xNamespace + "ResourceExtensionStatusList") != null)
                    {
                        XElement resExtStatusList = enumerable.First().Element(xNamespace + "ResourceExtensionStatusList");
                        // get the list of resource extension status
                        IEnumerable<XElement> extStatuses = from statusNode in resExtStatusList.Elements(xNamespace + "ResourceExtensionStatus")
                            where StringIsEqual(statusNode.Element(xNamespace + "HandlerName").Value,
                                MSExtAppHandlerPublisherName + "." + MSExtAppHandlerName)
                            select statusNode;
                        var xElements = extStatuses as XElement[] ?? extStatuses.ToArray();
                        if (xElements.Any())
                        {
                            // get the current EH status
                            deployData.SelectedVirtualMachine.ResourceExtensionStatus = ResourceExtensionStatus.ParseObjectFromXml(xElements.First(), xNamespace);
                            GetEHStatus(deployData, count);
                        }
                        else
                        {
                            AzureHelper.GetInProgressStatusAndMessage(deployData);
                        }
                    }
                    else
                    {
                        // the EH may not have been installed yet
                        AzureHelper.GetInProgressStatusAndMessage(deployData);
                    }
                }
            }
        }

        /// <summary>
        /// returns the status and message as InProgress
        /// </summary>
        /// <param name="deployData"></param>
        private static void GetInProgressStatusAndMessage(ResExtAzureDeploymentData deployData)
        {
            deployData.ResExtAzureDeploymentStatus = ResExtAzureDeploymentStatus.InProgress;
            string originalMsg = deployData.SummaryResultMessage.Message;
            deployData.SummaryResultMessage = new ResExtDeploymentResult
            {
                Category = HandlerStatusStates.Transitioning,
                Message = originalMsg + " . "
            };
        }

        /// <summary>
        /// gets the current EAH status
        /// </summary>
        /// <param name="deployData"></param>
        /// <param name="count"></param>
        internal static void UpdateVmRoleEhStatus(ResExtAzureDeploymentData deployData, long count)
        {
            const string uriFormat = "https://{0}/{1}/services/hostedservices/{2}/deployments/{3}";
            Uri uri = new Uri(String.Format(uriFormat, deployData.FrontEnd, deployData.SubscriptionId, 
                deployData.SelectedCloudService.Name, deployData.SelectedVirtualMachine.DeploymentName));

            // submit the request and get the response for the current deployment representation
            XDocument responseBody;
            HttpWebResponse response = MakeRestRequest(uri, "GET", deployData.MgmtCertificate, out responseBody);

            if (response!= null && response.StatusCode == HttpStatusCode.OK)
            {
                UpdateResourceExtensionStatus(deployData, responseBody, count);
            }
            else
            {
                string msg = String.Format("VM Role Authoring Tool encountered an error when querying for Virtual Machine: {0}", deployData.SelectedVirtualMachine.Name);
                deployData.ResExtAzureDeploymentStatus = ResExtAzureDeploymentStatus.Failed;
                deployData.SummaryResultMessage = new ResExtDeploymentResult
                {
                    Category = HandlerStatusStates.Error,
                    Message = "VM Role Authoring Tool encountered an error when querying for Virtual Machine : " + deployData.SelectedVirtualMachine.Name
                };
                MessageBox.Show(msg, DefinitionViewModel.ToolHeader, MessageBoxButton.OK, MessageBoxImage.Exclamation);
                
            }
        }
        /// <summary>
        /// make the update vm role rest request
        /// </summary>
        /// <param name="deployData"></param>
        /// <param name="responseId"></param>
        /// <returns></returns>
        internal static bool UpdateVMRoleRestRequest(ResExtAzureDeploymentData deployData, ref string responseId)
        {
            const string uriFormat = "https://{0}/{1}/services/hostedservices/{2}/deployments/{3}/roles/{4}";
            Uri uri = new Uri(String.Format(uriFormat, deployData.FrontEnd, deployData.SubscriptionId, 
                deployData.SelectedCloudService.Name, deployData.SelectedVirtualMachine.DeploymentName, deployData.SelectedVirtualMachine.Name));

            // submit the request and get the response for the current vmrole representation
            XDocument responseBody;
            HttpWebResponse response = MakeRestRequest(uri, "GET", deployData.MgmtCertificate, out responseBody);

            if (response !=null && response.StatusCode == HttpStatusCode.OK)
            {
                XDocument requestBody;
                // create the update deployment body
                requestBody = AddExtReferenceSettings(responseBody, deployData.AzureResExtBase64EncodedProtectedSettings);

                if (requestBody == null)
                {
                    // there was an error, return
                    string msg = String.Format("VM Role Authoring Tool encountered an error when querying for Virtual Machine for the Resource Extension: {0}", deployData.SelectedVirtualMachine.Name);
                    MessageBox.Show(msg, DefinitionViewModel.ToolHeader, MessageBoxButton.OK, MessageBoxImage.Exclamation);
                    return false;
                }

                // submit the update request and get the response
                XDocument putResponseBody;
                HttpWebResponse putResponse = MakeRestRequest(uri, "PUT", deployData.MgmtCertificate, out putResponseBody, requestBody.ToString(SaveOptions.OmitDuplicateNamespaces));
                // parse the put request response
                if (putResponse != null && putResponse.StatusCode == HttpStatusCode.Accepted)
                {
                    string[] xMsReqIds = putResponse.Headers.GetValues("x-ms-request-id");
                    if (xMsReqIds == null || xMsReqIds.Length <= 0)
                    {
                        string msg = String.Format("VM Role Authoring Tool encountered an error getting the status for the submission of the Resource Extension deployment for Virtual Machine: {0}",
                            deployData.SelectedVirtualMachine.Name);
                        MessageBox.Show(msg, DefinitionViewModel.ToolHeader, MessageBoxButton.OK, MessageBoxImage.Exclamation);
                        return false;
                    }
                    // return with response id
                    responseId = xMsReqIds[0];
                    return true;
                }
                string errMsg = String.Format("VM Role Authoring Tool encountered an error during submission of the Resource Extension deployment for Virtual Machine: {0}",
                    deployData.SelectedVirtualMachine.Name);
                MessageBox.Show(errMsg, DefinitionViewModel.ToolHeader, MessageBoxButton.OK, MessageBoxImage.Exclamation);
                return false;
            }
            else
            {
                string msg = String.Format("VM Role Authoring Tool encountered an error when querying for Virtual Machine: {0}", deployData.SelectedVirtualMachine.Name);
                MessageBox.Show(msg, DefinitionViewModel.ToolHeader, MessageBoxButton.OK, MessageBoxImage.Exclamation);
                return false;
            } 
        }

        /// <summary>
        /// check the status of the update vmrole request
        /// </summary>
        /// <param name="deployData"></param>
        /// <param name="responseId"></param>
        /// <param name="count"></param>
        internal static void GetUpdateVmRoleStatus(ResExtAzureDeploymentData deployData, string responseId, long count)
        {
            const string uriFormat = "https://{0}/{1}/operations/{2}";
            Uri uri = new Uri(String.Format(uriFormat, deployData.FrontEnd, deployData.SubscriptionId, responseId));

            // Submit the request and get the response
            XDocument responseBody;
            HttpWebResponse response = MakeRestRequest(uri, "GET", deployData.MgmtCertificate, out responseBody);

            if (response != null && response.StatusCode == HttpStatusCode.OK)
            {
                GetStatus(deployData, responseBody, count);
            }
            else
            {
                deployData.ResExtAzureDeploymentStatus = ResExtAzureDeploymentStatus.Failed;
                deployData.SummaryResultMessage = new ResExtDeploymentResult
                {
                    Category = HandlerStatusStates.Error,
                    Message = "VM Role Author Tool failed to get status of the deploy operation ",
                };
            }
        }

        /// <summary>
        /// parse the response from the get operation status call
        /// </summary>
        /// <param name="deployData"></param>
        /// <param name="xDocument"></param>
        /// <param name="count"></param>
        internal static void GetStatus(ResExtAzureDeploymentData deployData, XDocument xDocument, long count)
        {
            Debug.Assert(xDocument.Root != null, "xDocument.Root != null");
            XNamespace xNamespace = xDocument.Root.Name.Namespace;
            var xElement = xDocument.Root.Element(xNamespace + "Status");
            if (xElement != null)
            {
                string status = xElement.Value;

                switch(status)
                {
                    case "Succeeded":
                        deployData.ResExtAzureDeploymentStatus = ResExtAzureDeploymentStatus.CompletedWithSuccess;
                        deployData.SummaryResultMessage = new ResExtDeploymentResult
                        {
                            Category = HandlerStatusStates.Success,
                            Message = "Deployment of Resource Extension completed successfully."
                        };
                        break;

                    case "InProgress":
                        // issue a inprogress/pending message
                        deployData.ResExtAzureDeploymentStatus = ResExtAzureDeploymentStatus.InProgress;
                        deployData.SummaryResultMessage = new ResExtDeploymentResult
                        {
                            Category = HandlerStatusStates.Transitioning,
                            Message = deployData.SummaryResultMessage.Message + GetProgressIndicator(count)
                        };                    
                        break;

                    case "Failed":
                        deployData.ResExtAzureDeploymentStatus = ResExtAzureDeploymentStatus.Failed;
                        deployData.SummaryResultMessage = new ResExtDeploymentResult
                        {
                            Category = HandlerStatusStates.Error,
                            Message = "Deployment of Resource Extension failed.",
                        };
                        var element = xDocument.Root.Element(xNamespace + "Error");
                        if (element != null)
                        {
                            var xElement1 = element.Element(xNamespace + "Code");
                            if (xElement1 != null)
                                deployData.ErrorCode = xElement1.Value;
                        }
                        deployData.AdditionalResultMessages = new ObservableCollection<ResExtDeploymentResult>();
                        var element1 = xDocument.Root.Element(xNamespace + "Error");
                        if (element1 != null)
                        {
                            var xElement2 = element1.Element(xNamespace + "Message");
                            if (xElement2 != null)
                                deployData.AdditionalResultMessages.Add(new ResExtDeploymentResult
                                {
                                    Category = HandlerStatusStates.Error,
                                    Message =  xElement2.Value
                                });
                        }
                        break;
                }
            }
        }

        /// <summary>
        /// gets a progress indicator string to display
        /// </summary>
        /// <param name="count"></param>
        /// <returns></returns>
        private static string GetProgressIndicator(long count)
        {
            string returnValue = @" . . . . . . . ";
            return returnValue.Remove(((int)(count % 6)) * 2);
        }

        /// <summary>
        /// parse the reposnse from the get EAH status call
        /// </summary>
        /// <param name="deployData"></param>
        /// <param name="count"></param>
        internal static void GetEHStatus(ResExtAzureDeploymentData deployData, long count)
        {
            ResourceExtensionStatus res = deployData.SelectedVirtualMachine.ResourceExtensionStatus;
            if (res.Status == HandlerAggregateStatus.Ready && res.ExtensionSettingStatus != null)
            {
                switch (res.ExtensionSettingStatus.Status)
                {
                    case HandlerStatusStates.Success:
                        if (deployData.LastUpdatedTimestamp.HasValue &&
                            res.ExtensionSettingStatus != null &&
                            deployData.LastUpdatedTimestamp.Value == res.ExtensionSettingStatus.ConfigurationAppliedTime)
                        {
                            // the success message that was obtained was for the previous update
                            // return and retry
                            AzureHelper.GetInProgressStatusAndMessage(deployData);
                            return;
                        }
                        deployData.ResExtAzureDeploymentStatus = ResExtAzureDeploymentStatus.CompletedWithSuccess;
                        deployData.SummaryResultMessage = new ResExtDeploymentResult
                        {
                            Category = HandlerStatusStates.Success,
                            Message = "The Resource Extension was deployed successfully."
                        };
                        deployData.LastUpdatedTimestamp = res.ExtensionSettingStatus.ConfigurationAppliedTime;
                        break;

                    case HandlerStatusStates.Transitioning:
                        // issue a inprogress/pending message
                        deployData.ResExtAzureDeploymentStatus = ResExtAzureDeploymentStatus.InProgress;
                        deployData.SummaryResultMessage = new ResExtDeploymentResult
                        {
                            Category = HandlerStatusStates.Transitioning,
                            Message = (res.ExtensionSettingStatus.FormattedMessage != null) ?
                            res.ExtensionSettingStatus.FormattedMessage.Message :
                            "The configuration is being deployed" + GetProgressIndicator(count)
                        };

                        break;

                    case HandlerStatusStates.Error:
                        if (deployData.LastUpdatedTimestamp.HasValue &&
                            res.ExtensionSettingStatus != null &&
                            deployData.LastUpdatedTimestamp.Value == res.ExtensionSettingStatus.Timestamp)
                        {
                            // the success message that was obtained was for the previous update
                            // return and retry
                            AzureHelper.GetInProgressStatusAndMessage(deployData);
                            return;
                        }
                        deployData.ResExtAzureDeploymentStatus = ResExtAzureDeploymentStatus.Failed;
                        deployData.SummaryResultMessage = new ResExtDeploymentResult
                        {
                            Category = HandlerStatusStates.Error,
                            Message = (res.ExtensionSettingStatus.FormattedMessage != null) ?
                            res.ExtensionSettingStatus.FormattedMessage.Message :
                            "There was an error during the Resource Extension deployment."
                        };
                        deployData.LastUpdatedTimestamp = res.ExtensionSettingStatus.Timestamp;
                        break;

                    case HandlerStatusStates.Warning:
                        if (deployData.LastUpdatedTimestamp.HasValue &&
                           res.ExtensionSettingStatus != null &&
                           deployData.LastUpdatedTimestamp.Value == res.ExtensionSettingStatus.Timestamp)
                        {
                            // the warning message that was obtained was for the previous update
                            // return and retry
                            AzureHelper.GetInProgressStatusAndMessage(deployData);
                            return;
                        }
                        deployData.ResExtAzureDeploymentStatus = ResExtAzureDeploymentStatus.CompletedWithWarning;
                        deployData.SummaryResultMessage = new ResExtDeploymentResult
                        {
                            Category = HandlerStatusStates.Warning,
                            Message = (res.ExtensionSettingStatus.FormattedMessage != null) ?
                            res.ExtensionSettingStatus.FormattedMessage.Message :
                            "Resource Extension deployment completed with warnings."
                        };
                        deployData.LastUpdatedTimestamp = res.ExtensionSettingStatus.Timestamp;
                        break;
                }

                if (res.ExtensionSettingStatus.SubStatusList != null &&
                    res.ExtensionSettingStatus.SubStatusList.Count > 0)
                {
                    deployData.AdditionalResultMessages = new ObservableCollection<ResExtDeploymentResult>();
                    foreach (SubStatus st in res.ExtensionSettingStatus.SubStatusList)
                    {
                        deployData.AdditionalResultMessages.Add(new ResExtDeploymentResult
                        {
                            Category = st.Status,
                            Message = (st.FormattedMessage != null) ? st.FormattedMessage.Message :
                            OperationCodeHelper.GetMessageForDisplay(st)
                        });
                    }
                }
            }
            else if (res.Status == HandlerAggregateStatus.Installing)
            {
                deployData.ResExtAzureDeploymentStatus = ResExtAzureDeploymentStatus.InProgress;
                deployData.SummaryResultMessage = new ResExtDeploymentResult
                {
                    Category = HandlerStatusStates.Transitioning,
                    Message = "The Resource Extension is being installed " + GetProgressIndicator(count)
                };
            }
        }

        /// <summary>
        /// generates the update vmrole azure powershell script
        /// </summary>
        /// <param name="deployData"></param>
        /// <returns></returns>
        internal static string GetPowerShellScript(ResExtAzureDeploymentData deployData)
        {
            List<string> script = new List<string>();
            // get the settings in a powershell variable
            string quoted = string.Format("{0}", Regex.Replace(deployData.AzureResExtProtectedSettings, "([\\$\'\"`])", "`$1"));
            script.Add(String.Format("[string]$plainSettings = \"{0}\"" + Environment.NewLine,
               quoted));

            // get the vm context
            script.Add(String.Format("$vmContext = Get-AzureVM -ServiceName \"{0}\" -Name \"{1}\"" + Environment.NewLine, 
                deployData.SelectedCloudService.Name, deployData.SelectedVirtualMachine.Name));

            // set the extension
            script.Add(String.Format("$vm = Set-AzureVMExtension -ExtensionName \"{0}\" -Publisher \"{1}\" -Version \"{2}\" -PrivateConfiguration $plainSettings -VM $vmContext.VM" + Environment.NewLine,
                MSExtAppHandlerName, MSExtAppHandlerPublisherName, MSExtAppHandlerVersion));

            // call update on the vm role
            script.Add(String.Format("Update-AzureVM -ServiceName \"{0}\" -VM $vm -Name \"{1}\"" + Environment.NewLine, 
                deployData.SelectedCloudService.Name, deployData.SelectedVirtualMachine.Name));

            // create the script
            string result = String.Join(Environment.NewLine, script.ToArray());

            return result;
        }

        /// <summary>
        /// add or update the Extension References section
        /// </summary>
        /// <param name="xDocument"></param>
        /// <param name="protectedSettings"></param>
        /// <returns></returns>
        internal static XDocument AddExtReferenceSettings(XDocument xDocument, string protectedSettings)
        {
            // fetch the existing references
            if (xDocument.Root != null)
            {
                try
                {
                    XNamespace xNamespace = xDocument.Root.Name.Namespace;
                    XElement references = xDocument.Root.Element(xNamespace + "ResourceExtensionReferences");

                    if (references != null)
                    {
                        // there is a ResourceExtReferences node
                        IEnumerable<XElement> reference =
                            from node in references.Elements(xNamespace + "ResourceExtensionReference")
                            where
                                string.Equals(node.Element(xNamespace + "Name").Value, MSExtAppHandlerName,
                                    StringComparison.InvariantCultureIgnoreCase) &&
                                string.Equals(node.Element(xNamespace + "Publisher").Value, MSExtAppHandlerPublisherName,
                                    StringComparison.InvariantCultureIgnoreCase)
                            select node;

                        var xElements = reference as XElement[] ?? reference.ToArray();
                        if (!xElements.Any())
                        {
                            // no MS EAH Extension present
                            // construct the node for ResourceExtensionReference
                            string reqExtRef = String.Format(ResourceExtReference, xNamespace.NamespaceName,
                                MSExtAppHandlerPublisherName, MSExtAppHandlerName, MSExtAppHandlerVersion, protectedSettings);
                            XElement extRef = XElement.Parse(reqExtRef);
                            // add it to the existing references 
                            references.Add(extRef);
                        }
                        else
                        {
                            // a MS EAH Extension is present
                            // construct the node for ResourceExtensionReferenceParameterValues
                            string reqExtRefParamValue = String.Format(ResourceExtReferenceParamValues,
                                xNamespace.NamespaceName,
                                protectedSettings);
                            XElement extRefParamValues = XElement.Parse(reqExtRefParamValue);

                            // add the correct version
                            xElements.First().Element(xNamespace + "Version").Value = MSExtAppHandlerVersion;
                            
                            // add or replace the ResourceExtensionParameterValue value node 
                            XElement repvs = xElements.First().Element(xNamespace + "ResourceExtensionParameterValues");
                            if (repvs == null)
                            {
                                xElements.First().Add(extRefParamValues);
                            }
                            else
                            {
                                repvs.ReplaceWith(extRefParamValues);
                            }
                        }
                    }
                    else
                    {
                        // there is no ResourceExtReferences node, create it

                        // construct the MS EAH Extension ResourceExtensionReference
                        string reqExtRef = String.Format(ResourceExtReference, xNamespace.NamespaceName,
                            MSExtAppHandlerPublisherName, MSExtAppHandlerName, MSExtAppHandlerVersion, protectedSettings);

                        // construct the ResourceExtensionReference node
                        string reqExtReferences = String.Format(ResourceExtReferences, xNamespace.NamespaceName,
                            reqExtRef);
                        XElement extReferences = XElement.Parse(reqExtReferences);

                        // add it after the ConfigurationSets XElement 
                        XElement configurationSets = xDocument.Root.Element(xNamespace + "ConfigurationSets");
                        if (configurationSets != null)
                        {
                            configurationSets.AddAfterSelf(extReferences);
                        }
                        else
                        {
                            // error
                            return null;
                        }
                    }
                }
                catch (Exception)
                {
                    // error
                    return null;
                }
            }
            else
            {
                // error
                return null;
            }

            return xDocument;
        }

        /// <summary>
        /// adjust the relative paths in the app payload section to point to virtual hierachy paths in blob storage  
        /// </summary>
        /// <param name="resExtDef"></param>
        /// <param name="deployData"></param>
        /// <returns></returns>
        internal static VMRoleResourceExtensionDefinition AdjustApplicationPayloadPaths(VMRoleResourceExtensionDefinition resExtDef,
            ResExtAzureDeploymentData deployData)
        {
            // check in there is payload in the rex ext 
            if (deployData.HasPayload)
            {
                // set the location to the container's sas url
                string originalLocation = resExtDef.DataPackage.Location;  

                // generate the sas url 
                resExtDef.DataPackage.Location = deployData.SelectedStorageContainer.CloudBlobContainer.StorageUri.PrimaryUri.ToString() +
                    deployData.SelectedStorageContainer.CloudBlobContainer.GetSharedAccessSignature(new SharedAccessBlobPolicy()
                    {
                        Permissions = SharedAccessBlobPermissions.Read | SharedAccessBlobPermissions.List,
                        SharedAccessExpiryTime = DateTime.UtcNow.AddHours(12)
                    });

                // adjust the relative paths for the resources
                foreach (ApplicationPayload ap in resExtDef.ExtensionSettings.ApplicationProfile.ApplicationPayload)
                {
                    ap.RelativePath = BlobPathCombine(originalLocation, ap.RelativePath);
                }
            }
            return resExtDef;
        }

        /// <summary>
        /// Remove uploaded resource from azure storage at the end of deployment
        /// </summary>
        /// <param name="deployData"></param>
        internal static void RemoveUploadedResExtesources(ResExtAzureDeploymentData deployData)
        {
            // only delete if there was payload
            if (deployData.HasPayload)
            {
                // go through the list of upload reference Paths
                foreach (string referencePath in deployData.ResourceReferencePaths)
                {
                    CloudBlockBlob cbb = deployData.SelectedStorageContainer.CloudBlobContainer.GetBlockBlobReference(referencePath);

                    if (!cbb.Exists())
                    {
                        // this is dir, get a flat list of blobs and delete them
                        CloudBlobDirectory cbd = deployData.SelectedStorageContainer.CloudBlobContainer.GetDirectoryReference(referencePath);
                        foreach (IListBlobItem blobItem in cbd.ListBlobs(true))
                        {
                            if (blobItem is CloudBlockBlob)
                            {
                                (blobItem as CloudBlockBlob).DeleteIfExists();
                            }
                        }
                    }
                    else
                    {
                        // this is a single file
                        if (cbb != null)
                        {
                            cbb.DeleteIfExists();
                        }
                    }
                }
            }
        }

        /// <summary>
        /// upload resources specified in the resext to Azure storage
        /// </summary>
        /// <param name="resExtViewModel"></param>
        /// <param name="deployData"></param>
        /// <param name="uploadRequired"></param>
        /// <returns></returns>
        internal static bool UploadResExtResources(VmRoleResourceExtensionViewModel resExtViewModel,
            ResExtAzureDeploymentData deployData, bool uploadRequired)
        {
            // return if no upload is required
            if (!deployData.HasPayload || deployData.AreResourcesUploaded)
            {
                return true;
            }

            // get the locations of the DataPackage source, workking dir and teh external resource dir
            string srcLocation = GetDataPackageLocation(resExtViewModel);
            string workingDir = resExtViewModel.ResourceExtensionStorageInfo.WorkingDir;
            string externalPackageLocation = Path.GetDirectoryName(resExtViewModel.ResourceExtensionStorageInfo.WorkingDir);

            // location of the resource locally on this machine
            string relativeLocationInContainer = null; ;

            // the root folder for all the resources that will be uploaded
            if (uploadRequired)
            {
                relativeLocationInContainer = srcLocation;
            }

            // go through all the resources in the resext
            List<Task> uploads = new List<Task>();
            foreach (ApplicationPayload ap in resExtViewModel.ResourceExtensionDefinition.ExtensionSettings.ApplicationProfile.ApplicationPayload)
            {
                string contentLocation;
                if (ExternalPayloadStartingWithPrefixExists(resExtViewModel, ap.RelativePath))
                {
                    // this is an external resource
                    contentLocation = Path.Combine(externalPackageLocation, srcLocation);
                }
                else
                {
                    // this is a local resource
                    contentLocation = Path.Combine(workingDir, srcLocation);
                }

                // upload it to the container
                string pathOnSource = Path.Combine(contentLocation, ap.RelativePath);
                
                if (Directory.Exists(pathOnSource) || File.Exists(pathOnSource))
                {
                    // only upload if uploadRequired
                    if (uploadRequired)
                    {
                        if (File.Exists(pathOnSource))
                        {
                            // this is a file
                            string relativePathOnContainer = BlobPathCombine(relativeLocationInContainer, ap.RelativePath);
                            // add path to the list of resource references as we may need for cleanup 
                            deployData.ResourceReferencePaths.Add(relativePathOnContainer);

                            Task task = UploadFileToAzure(deployData, relativePathOnContainer, pathOnSource);
                            uploads.Add(task);
                        }
                        else
                        {
                            // this is a directory
                            // get a flattened list of files in the dir
                            List<KeyValuePair<string, string>> listOfFiles = GetFileListFromFolder(pathOnSource, relativeLocationInContainer);
                            // upload them to the blob storage
                            foreach (KeyValuePair<string, string> fileKV in listOfFiles)
                            {
                                // add path to the list of resource references as we may need for cleanup 
                                deployData.ResourceReferencePaths.Add(fileKV.Value);

                                Task task = UploadFileToAzure(deployData, fileKV.Value, fileKV.Key);
                                uploads.Add(task);
                            }
                        }
                    }
                }
                else
                {
                    // indicate that the payload does not exist
                    MessageBox.Show("Uploading resources will fail as the resource corresponding " +
                                    "to the Application Payload with Relative Path \"" + ap.RelativePath +
                                    "\" was not found on this machine.", DefinitionViewModel.ToolHeader, MessageBoxButton.OK, MessageBoxImage.Exclamation);
                    return false;
                }
            }

            // wait for uploads to finish
            if (uploadRequired)
            {
                Task.WaitAll(uploads.ToArray());
            }
            return true;
        }

        /// <summary>
        /// checks whether resources from the ResExt are in the container
        /// </summary>
        /// <param name="resExtViewModel"></param>
        /// <param name="deployData"></param>
        /// <returns></returns>
        internal static bool CheckUploadedResExtResources(VmRoleResourceExtensionViewModel resExtViewModel,
          ResExtAzureDeploymentData deployData)
        {
            // return if no upload is required
            if (!deployData.HasPayload)
            {
                return true;
            }

            // get the locations of the DataPackage source, workking dir and the external resource dir
            string srcLocation = GetDataPackageLocation(resExtViewModel);

            // location of the resource locally on this machine
            List<string> missingResources = new List<string>();
            
            // go through all the resources in the resext
            List<Task> uploads = new List<Task>();
            foreach (ApplicationPayload ap in resExtViewModel.ResourceExtensionDefinition.ExtensionSettings.ApplicationProfile.ApplicationPayload)
            {
                // check whether it is on the container
                string relPathOnContainer = BlobPathCombine(srcLocation, ap.RelativePath);

                CloudBlockBlob cbb = deployData.SelectedStorageContainer.CloudBlobContainer.GetBlockBlobReference(relPathOnContainer);
                
                if (!cbb.Exists())
                {
                    // check whether this is directory as per the RexExt relpath
                    CloudBlobDirectory cbd = deployData.SelectedStorageContainer.CloudBlobContainer.GetDirectoryReference(relPathOnContainer);
                    if (!cbd.ListBlobs().Any())
                    {
                        missingResources.Add(relPathOnContainer);
                        continue;
                    }
                }
                
                // add this to list of reference paths as it may be required for deletion
                deployData.ResourceReferencePaths.Add(relPathOnContainer);
            }

            // check whether any resource is missing
            if (missingResources.Count > 0)
            {
                string errorMessage = string.Join(",", missingResources.ToArray());
                errorMessage = " The following resources (" + errorMessage + ") could not be located in the " +
                    deployData.SelectedStorageContainer.Name + " container.";
                MessageBox.Show(errorMessage, DefinitionViewModel.ToolHeader, MessageBoxButton.OK, MessageBoxImage.Exclamation);
                return false;
            }

            return true;
        }
        /// <summary>
        /// upload a single resource to Azure storage with relative path for the virtual hierarchy
        /// </summary>
        /// <param name="deployData"></param>
        /// <param name="relativePathOnContainer"></param>
        /// <param name="pathOnSource"></param>
        /// <returns></returns>
        private static async Task UploadFileToAzure(ResExtAzureDeploymentData deployData, string relativePathOnContainer, string pathOnSource)
        {
            CloudBlockBlob cbb = deployData.SelectedStorageContainer.CloudBlobContainer.GetBlockBlobReference(relativePathOnContainer);
            await cbb.UploadFromFileAsync(pathOnSource, FileMode.Open).ConfigureAwait(false);
        }

        /// <summary>
        /// traverse the input path and get a list of files to upload along with the Azure storage virtual hierarchy paths
        /// </summary>
        /// <param name="folderPath"></param>
        /// <param name="azureRelativePath"></param>
        /// <returns></returns>
        private static List<KeyValuePair<string,string>> GetFileListFromFolder(string folderPath, string azureRelativePath)
        {
            List<KeyValuePair<string, string>> results = new List<KeyValuePair<string, string>>();

            if ((File.GetAttributes(folderPath) & FileAttributes.Directory) == 0)
            {
                // is a file
                results.Add(new KeyValuePair<string, string>(folderPath, BlobPathCombine(azureRelativePath, Path.GetFileName(folderPath))));
            }
            else
            {
                // is a directory
                foreach (string fileEntry in Directory.EnumerateFileSystemEntries(folderPath))
                {
                    // directory
                    results.AddRange(GetFileListFromFolder(fileEntry, BlobPathCombine(azureRelativePath, Path.GetFileName(folderPath))));
                }
            }
            return results;
        }

        /// <summary>
        /// combine input paths with a "/" separator
        /// </summary>
        /// <param name="prefix"></param>
        /// <param name="suffix"></param>
        /// <returns></returns>
        private static string BlobPathCombine(string prefix, string suffix)
        {
            return prefix + DeLimiter + suffix; 
        }

        /// <summary>
        /// extract the location field from the data package
        /// </summary>
        /// <param name="resExtViewModel"></param>
        /// <returns></returns>
        private static string GetDataPackageLocation(VmRoleResourceExtensionViewModel resExtViewModel)
        {
            if (resExtViewModel.ResourceExtensionDefinition == null ||
                resExtViewModel.ResourceExtensionDefinition.DataPackage == null)
            {
                return string.Empty;
            }

            return resExtViewModel.ResourceExtensionDefinition.DataPackage.Location;
        }

        
        /// <summary>
        /// returns true if the prefix exists in the external payload section indicating that it is external resource 
        /// </summary>
        /// <param name="resExtViewModel"></param>
        /// <param name="prefix"></param>
        /// <returns></returns>
        private static bool ExternalPayloadStartingWithPrefixExists(VmRoleResourceExtensionViewModel resExtViewModel, string prefix)
        {
            if (string.IsNullOrEmpty(prefix) ||
                resExtViewModel.ResourceExtensionDefinition == null ||
                resExtViewModel.ResourceExtensionDefinition.DataPackage == null ||
                resExtViewModel.ResourceExtensionDefinition.DataPackage.ExternalPayload == null)
            {
                return false;
            }

            bool result = false;
            foreach (ExternalPayload extPld in resExtViewModel.ResourceExtensionDefinition.DataPackage.ExternalPayload)
            {
                if (!string.IsNullOrEmpty(extPld.RelativePath))
                {
                    if (extPld.RelativePath.StartsWith(prefix, StringComparison.Ordinal) ||
                        extPld.RelativePath.StartsWith("./" + prefix, StringComparison.Ordinal))
                    {
                        result = true;
                        break;
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// return a list of virtual machines as specified in the xDocument
        /// </summary>
        /// <param name="xDocument"></param>
        /// <returns></returns>
        internal static List<VirtualMachine> GetVirtualMachines(XDocument xDocument)
        {
            List<VirtualMachine> vms = new List<VirtualMachine>();
            if (xDocument.Root != null)
            {
                XNamespace xNamespace = xDocument.Root.Name.Namespace;
                var deployments = xDocument.Root.Elements(xNamespace + "Deployments").Elements(xNamespace + "Deployment");

                foreach (XElement node in deployments)
                {
                    var xElement = node.Element(xNamespace + "Name");
                    if (xElement != null)
                    {
                        string deploymentName = xElement.Value;

                        var element = node.Element(xNamespace + "RoleInstanceList");
                        if (element != null)
                        {
                            var roles = element.Elements(xNamespace + "RoleInstance");
                            foreach (XElement role in roles)
                            {
                                var xElement1 = role.Element(xNamespace + "RoleName");
                                if (xElement1 != null)
                                {
                                    string vmName = xElement1.Value;
                                    VirtualMachine vm = new VirtualMachine(vmName, deploymentName);

                                    if (role.Element(xNamespace + "GuestAgentStatus") != null)
                                    {
                                        // get the guest agent status
                                        vm.GuestAgentStatus = GuestAgentStatus.ParseObjectFromXml(role.Element(xNamespace + "GuestAgentStatus"), xNamespace);
                                    }

                                    if (role.Element(xNamespace + "ResourceExtensionStatusList") != null)
                                    {
                                        XElement resExtStatusList = roles.First().Element(xNamespace + "ResourceExtensionStatusList");
                                        // get the resource entension status
                                        IEnumerable<XElement> extStatuses = from statusNode in resExtStatusList.Elements(xNamespace + "ResourceExtensionStatus")
                                            where StringIsEqual(statusNode.Element(xNamespace + "HandlerName").Value,
                                                MSExtAppHandlerPublisherName + "." + MSExtAppHandlerName)
                                            select statusNode;
                                        var xElements = extStatuses as XElement[] ?? extStatuses.ToArray();
                                        if (xElements.Any())
                                        {
                                            vm.ResourceExtensionStatus = ResourceExtensionStatus.ParseObjectFromXml(xElements.First(), xNamespace);
                                        }
                                    }

                                    // add the vm
                                    vms.Add(vm);
                                }
                            }
                        }
                    }
                }
            }

            return vms;
        }

        /// <summary>
        /// return a list of cloud services as specified in the xDocument
        /// </summary>
        /// <param name="xDocument"></param>
        /// <returns></returns>
        internal static List<CloudService> GetCloudServices(XDocument xDocument)
        {
            List<XmlNode> instanceNames = GetNodes(xDocument.ToString(SaveOptions.OmitDuplicateNamespaces), "ServiceName");

            return instanceNames.Select(node => new CloudService(node.InnerXml)).ToList();
        }

        /// <summary>
        /// return a list of storage accounts as specified in the xDocument
        /// </summary>
        /// <param name="xDocument"></param>
        /// <param name="deployData"></param>
        /// <returns></returns>
        internal static List<StorageAccount> GetStorageAccounts(XDocument xDocument, ResExtAzureDeploymentData deployData)
        {
            List<StorageAccount> sas = new List<StorageAccount>();

            // get all storage accounts
            List<XmlNode> storageAccounts = GetNodes(xDocument.ToString(SaveOptions.OmitDuplicateNamespaces), "StorageService");

            // get the keys for the storage accounts
            foreach (XmlNode node in storageAccounts)
            {
                // walk through storage accounts
                // get account name
                List<XmlNode> accountName = GetNodes(node, "ServiceName");
                string storageAccountName = accountName[0].InnerXml;

                // get blob endpoint
                List<XmlNode> endpoints = GetNodes(node, "Endpoint");
                
                XmlNode blobEndpoint = (from selectNode in endpoints
                                      where selectNode.InnerXml.ToLower().Contains(".blob.") == true
                                      select selectNode).First();

                if (blobEndpoint != null)
                {
                    string uriFormat = "https://{0}/{1}/services/storageservices/{2}/keys";
                    Uri uri = new Uri(String.Format(uriFormat, deployData.FrontEnd, deployData.SubscriptionId, storageAccountName));

                    // Submit the request and get the response
                    XDocument responseBody;
                    HttpWebResponse response = MakeRestRequest(uri, "GET", deployData.MgmtCertificate, out responseBody);

                    if (response != null && response.StatusCode == HttpStatusCode.OK)
                    {
                        List<XmlNode> storageKeys = GetNodes(responseBody.ToString(SaveOptions.OmitDuplicateNamespaces), "Primary");
                        if (storageKeys.Count > 0)
                        {
                            sas.Add(new StorageAccount(storageAccountName, blobEndpoint.InnerXml, storageKeys[0].InnerXml));
                        }
                    }
                    else
                    {
                        throw new Exception("Accessing Storage Keys failed");
                    }
                }
            }

            return sas;
        }


        /// <summary>
        /// set the MS EAH Version to use for the deployment
        /// </summary>
        /// <param name="xDocument"></param>
        /// <returns></returns>
        internal static void SetMSExtAppHandlerVersion(XDocument xDocument)
        {
            if (xDocument.Root != null)
            {
                XNamespace xNamespace = xDocument.Root.Name.Namespace;
                IEnumerable<Version> resExtensions = from node in xDocument.Root.Elements(xNamespace + "ResourceExtension")
                    select  new Version(node.Element(xNamespace + "Version").Value);

                var extensions = resExtensions as Version[] ?? resExtensions.ToArray();
                if (extensions.Any())
                {
                    Version verToUse = extensions.Max();
                    // use only the major.* format
                    MSExtAppHandlerVersion = verToUse.Major.ToString() + ".*";
                }
                else
                {
                    MSExtAppHandlerVersion = string.Empty;
                    string msg = "The tool could not locate the Extension Handler to use. Please verify the Extension Handler settings in the tool's config file and restart the deployment";
                    MessageBox.Show(msg, DefinitionViewModel.ToolHeader, MessageBoxButton.OK, MessageBoxImage.Exclamation);
                }
            }
        }

        /// <summary>
        /// compare if two strings are equal
        /// </summary>
        /// <param name="source"></param>
        /// <param name="target"></param>
        /// <returns></returns>
        public static bool StringIsEqual(string source, string target)
        {
            if (String.Equals(source, target, StringComparison.InvariantCultureIgnoreCase))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }
}
